﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Net;
using ARSoft.Tools.Net.Dns;
using ARSoft.Tools.Net;
using System.Net.Sockets;

namespace simpleDns
{
    class dnsServer
    {
        private DnsServer server;
        private TaskFactory tf = new TaskFactory();
        private Dictionary<string, byte[]> cache = new Dictionary<string, byte[]>();
        public Dictionary<string, DOMAIN> domains;
        public List<IPAddress> _dnsServers;
        private IPAddress[] dnsServers
        {
            get
            {
                return _dnsServers.ToArray();
            }
        }
        public dnsServer()
        {
            _dnsServers = new List<IPAddress>();
            domains = new Dictionary<string, DOMAIN>();

        }

        async Task Server_QueryReceived(object sender, QueryReceivedEventArgs e)
        {
            DnsMessage query;
            DnsMessage response=null;
            try
            {
                query = e.Query as DnsMessage;
                if (query == null)
                    return;
                try
                {
                    if (query != null && (query.Questions.Count == 1))
                    {
                        DnsQuestion q = query.Questions[0];
                        
                        if (settings.serviceMode == (int)ServiceMode.远程优先本地接锅)
                            response = await getRecordRomote(dnsServers, q);
                        if (!(settings.serviceMode == (int)ServiceMode.仅远程 || (settings.serviceMode == (int)ServiceMode.远程优先本地接锅 && response!=null)))
                        {
                            string[] doms = q.Name.Labels;
                            string dom1 = string.Format("{0}.{1}", doms[doms.Length - 2], doms[doms.Length - 1]);
                            string dom2 = null;
                            if (doms.Length > 2)
                            {
                                dom2 = string.Format("{0}.{1}.{2}", doms[doms.Length - 3], doms[doms.Length - 2], doms[doms.Length - 1]);
                            }
                            DOMAIN xd = null;
                            if ((dom2 != null && domains.ContainsKey(dom2)))
                                xd = domains[dom2];
                            if (domains.ContainsKey(dom1))
                                xd = domains[dom1];
                            if (xd != null && xd.enable)
                            {
                                if (xd.dnsMode && xd.dnsRecords.Count > 0)
                                {
                                    if (xd.dnsRecords.Count != xd.dnsIpSort.Length)
                                        xd.sortDns();
                                    IPAddress[] xdns = new IPAddress[xd.dnsIpSort.Length];
                                    xd.dnsIpSort.CopyTo(xdns, 0);
                                    //dnsServers.CopyTo(xdns, xd.dnsIpSort.Length);
                                    response = await getRecordRomote(xdns, q); ;
                                }
                                if (!xd.dnsMode && xd.aRecords.Count > 0 && q.RecordType == RecordType.A && q.RecordClass == RecordClass.INet)
                                {
                                    foreach (ARECORD ar in xd.aRecords.Values)
                                    {
                                        if (ar.enable && q.Name.ToString().TrimEnd('.') == string.Format("{0}.{1}", ar.name, xd.name))
                                        {
                                            response.AnswerRecords.Add(new ARecord(q.Name, 3600, IPAddress.Parse(ar.value)));
                                        }
                                    }
                                }
                            }
                        }

                        if (settings.serviceMode == (int)ServiceMode.仅远程 || (settings.serviceMode == (int)ServiceMode.本地优先远程接锅 && response == null))
                        {
                            response = await getRecordRomote(dnsServers,q);
                        }

                    }
                    else
                    {
                        settings.log.add("no question get", "resove error");
                    }
                }
                catch (Exception ex)
                {
                    settings.log.add(ex.Message, "resove error");
                }
                finally
                {

                    if (response==null)
                    {
                        response.ReturnCode = ReturnCode.NoError;
                        settings.log.add("", "resove error");
                    }
                    else
                    {
                        response.TransactionID = query.TransactionID;
                        settings.log.add(string.Format("{0}:{1} record items",query.Questions[0].Name ,response.AnswerRecords.Count), "resove done");
                    }
                    
                    e.Response = response;
                }
            }
            catch (Exception ex)
            {
                settings.log.add(ex.Message, "resove low error");
            }

        }
        Task<DnsMessage> getRecordRomote(IPAddress[] dnss, DnsQuestion q)
        {
            return Task.Run(() =>
            {
                try
                {
                    DnsMessage m = null;
                    for(int i = 0; i < dnss.Length; i++)
                    {
                        DnsClient dc = new DnsClient(dnss[i],3000);
                        m = dc.Resolve(DomainName.Parse(q.Name.ToString().Trim('.')), q.RecordType, q.RecordClass);
                        if (m != null)
                        {
                            break;
                        }
                    }
                    return m;
                }
                catch (Exception ex)
                {
                    settings.log.add(string.Format("domain name :{0} ; exception message: {1}" ,q.Name.ToString(), ex.Message),"resolver error");
                    return null;
                }

            });
        }
        public bool setDomain(DOMAIN ym)
        {
            try
            {
                if (ym.enable == false)
                {
                    delDomain(ym);
                }
                else
                {
                    if (ym.dnsMode)
                        ym.sortDns();
                    if (domains.ContainsKey(ym.name))
                    {
                        domains[ym.name] = ym;
                    }
                    else
                    {
                        domains.Add(ym.name, ym);
                    }
                }
                return true;
            }
            catch
            {
                return false;
            }
        }
        public bool delDomain(DOMAIN ym)
        {
            if (domains.ContainsKey(ym.name))
            {
                domains.Remove(ym.name);
                return true;
            }
            else
            {
                return false;
            }
        }
        public bool delDomain(string ym)
        {
            if (domains.ContainsKey(ym))
            {
                domains.Remove(ym);
                return true;
            }
            else
            {
                return false;
            }
        }
        public void start()
        {
            server = new DnsServer(IPAddress.Parse(settings.serverIp), settings.udpNum, settings.tcpNum);
            server.QueryReceived += Server_QueryReceived;
            server.Start();
        }
        public void stop()
        {
            settings.log.dump();
            if (server != null)
                server.Stop();
            server = null;
        }
    }
}
